﻿#region Using Statements
using System;
using System.Diagnostics;
using System.Globalization;
#endregion

namespace DirectZZT.Shared.Scripting
{
    /// <summary>
    /// Specifies one flag/global variable.
    /// In ZZT-oop, this has no value (the flag alone being present is sufficient enough).
    /// In Powerscript, this acts as a variable and can have a value.
    /// </summary>
    [Serializable()]
    [DebuggerDisplay("Value = {_value}, TypeCode = {Type}")]
    public sealed class ScriptFlag
    {
        #region Fields

        private object _value;

        #endregion

        #region Properties

        /// <summary>
        /// Returns the type code of this flag.
        /// </summary>
        public TypeCode Type { get; private set; }

        #endregion

        #region Methods

        /// <summary>
        /// Returns the raw value. For convenience methods, please use the extension methods.
        /// </summary>
        /// <returns>The raw flag value.</returns>
        public object GetValue()
        {
            return _value;
        }

        /// <summary>
        /// Sets the raw value and the type code accordingly to the value.
        /// </summary>
        /// <param name="value">The value to set.</param>
        public void SetValue(object value)
        {
            _value = value;
            Type = Convert.GetTypeCode(_value);
        }

        /// <summary>
        /// Creates a reversible string from this instance.
        /// The returned string must be formatted so it can be converted back!
        /// </summary>
        /// <returns>A reversible string from this instance.</returns>
        public string ConvertTo()
        {
            if (_value == null)
            {
                return "";
            }
            return string.Format(CultureInfo.InvariantCulture, "{0};{1}", this.Type, _value.ToString());
        }

        /// <summary>
        /// Converts the value from the string into this instance.
        /// </summary>
        /// <param name="value">The value string which was created by <see cref="M:ConvertTo()"/> method.</param>
        public void ConvertFrom(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                SetValue(null);
            }
            else
            {
                string[] tokens = value.Split(';');

                TypeCode type = TypeCode.Empty;
                if (Enum.TryParse<TypeCode>(tokens[0], out type))
                {
                    this.Type = type;
                }

                // check the type code and perform conversion accordingly
                switch (type)
                {
                    case TypeCode.Empty:
                        this.SetValue(null);
                        break;
                    case TypeCode.Object:
                        {
                            // TODO: define interface IParseable with methods "ParseFrom(string)" and "string ParseTo()"
                        } break;
                    case TypeCode.Boolean:
                        this.SetValue(Convert.ToBoolean(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Byte:
                        this.SetValue(Convert.ToByte(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Char:
                        this.SetValue(Convert.ToChar(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.DateTime:
                        this.SetValue(Convert.ToDateTime(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Decimal:
                        this.SetValue(Convert.ToDecimal(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Double:
                        this.SetValue(Convert.ToDouble(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Int16:
                        this.SetValue(Convert.ToInt16(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Int32:
                        this.SetValue(Convert.ToInt32(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Int64:
                        this.SetValue(Convert.ToInt64(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.SByte:
                        this.SetValue(Convert.ToSByte(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Single:
                        this.SetValue(Convert.ToSingle(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.String:
                        this.SetValue(Convert.ToString(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.UInt16:
                        this.SetValue(Convert.ToUInt16(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.UInt32:
                        this.SetValue(Convert.ToUInt32(tokens[1], CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.UInt64:
                        this.SetValue(Convert.ToUInt64(tokens[1], CultureInfo.InvariantCulture));
                        break;
                }

            }
        }

        #endregion

        #region Helper methods

        ///// <summary>
        ///// Tries to convert the value of the given flag to an Int32 and returns <c>null</c> if it couldn't be converted.
        ///// </summary>
        ///// <param name="flag">The flag.</param>
        ///// <returns>The value of the given flag as an Int32 and returns <c>null</c> if it couldn't be converted.</returns>
        //public Nullable<int> GetInt32()
        //{
        //    int value = 0;
        //    if(int.
        //    return value;
        //}

        #endregion

        #region Factory

        /// <summary>
        /// Returns a new <see cref="ScriptFlag"/> instance with the given value converted.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static ScriptFlag From(string value)
        {
            ScriptFlag flag = new ScriptFlag();
            flag.ConvertFrom(value);
            return flag;
        }

        /// <summary>
        /// Returns an empty script flag.
        /// </summary>
        public static ScriptFlag Empty
        {
            get { return new ScriptFlag(); }
        }

        #endregion
    }
}
